import pango

import gtk
from gtk import glade

import actions


INITIAL_TEXT = """
    <b>This is bold</b>. <i>This is italic</i>
    <b><i>This is bold, italic, and <u>underlined!</u></i></b>
    This is <span color="blue">blue</span>,
    <span color="red">red</span> and
    <span color="green">green</span>
    some <span size="20000">larger</span> text
    """


class PangoBuffer(object):

    desc_to_attr_table = {
        'family': [pango.AttrFamily, ""],
        'style': [pango.AttrStyle, pango.STYLE_NORMAL],
        'variant': [pango.AttrVariant, pango.VARIANT_NORMAL],
        'weight': [pango.AttrWeight, pango.WEIGHT_NORMAL],
        'stretch': [pango.AttrStretch, pango.STRETCH_NORMAL],
        'size': [pango.AttrSize, 1],
        }

    pango_translation_properties = {
        # pango ATTR TYPE : (pango attr property / tag property)
        pango.ATTR_SIZE : 'size',
        pango.ATTR_WEIGHT: 'weight',
        pango.ATTR_UNDERLINE: 'underline',
        pango.ATTR_STRETCH: 'stretch',
        pango.ATTR_VARIANT: 'variant',
        pango.ATTR_STYLE: 'style',
        pango.ATTR_SCALE: 'scale',
        pango.ATTR_STRIKETHROUGH: 'strikethrough',
        pango.ATTR_RISE: 'rise',
        }

    attval_to_markup = {
        'underline': {
            pango.UNDERLINE_SINGLE: 'single',
            pango.UNDERLINE_DOUBLE: 'double',
            pango.UNDERLINE_LOW: 'low',
            pango.UNDERLINE_NONE: 'none',
            },
        'stretch': {
            pango.STRETCH_ULTRA_EXPANDED: 'ultraexpanded',
            pango.STRETCH_EXPANDED: 'expanded',
            pango.STRETCH_EXTRA_EXPANDED: 'extraexpanded',
            pango.STRETCH_EXTRA_CONDENSED: 'extracondensed',
            pango.STRETCH_ULTRA_CONDENSED: 'ultracondensed',
            pango.STRETCH_CONDENSED: 'condensed',
            pango.STRETCH_NORMAL: 'normal',
            },
        'variant': {
            pango.VARIANT_NORMAL: 'normal',
            pango.VARIANT_SMALL_CAPS: 'smallcaps',
            },
        'style': {
            pango.STYLE_NORMAL: 'normal',
            pango.STYLE_OBLIQUE: 'oblique',
            pango.STYLE_ITALIC: 'italic',
            },
        'stikethrough': {
            1: 'true',
            True: 'true',
            0: 'false',
            False: 'false',
            },
        'size': {
            pango.SCALE_LARGE: 'larger',
            pango.SCALE_SMALL: 'smaller',
            },
        }

    def __init__(self, txt, buf):
        self.tagdict = {}
        self.tags = {}
        self.buf = buf
        self.set_text(txt)

    def set_text(self, txt):
        self.parsed,self.txt,self.separator = pango.parse_markup(txt,u'0')
        self.attrIter = self.parsed.get_iterator()
        self.add_iter_to_buffer()
        while self.attrIter.next():
            self.add_iter_to_buffer()

    def add_iter_to_buffer(self):
        range = self.attrIter.range()
        font,lang,attrs = self.attrIter.get_font()
        tags = self.get_tags_from_attrs(font, lang,attrs)
        text = self.txt[range[0]:range[1]]
        if tags:
            self.buf.insert_with_tags(self.buf.get_end_iter(), text, *tags)
        else:
            self.buf.insert_with_tags(self.buf.get_end_iter(), text)

    def get_tags_from_attrs(self, font, lang, attrs):
        tags = []
        if font:
            font, fontattrs = self.fontdesc_to_attrs(font)
            fontdesc = font.to_string()
            if fontattrs:
                attrs.extend(fontattrs)
            if fontdesc and fontdesc != 'Normal':
                if not self.tags.has_key(font.to_string()):
                    tag=self.buf.create_tag()
                    tag.set_property('font-desc',font)
                    if not self.tagdict.has_key(tag):
                        self.tagdict[tag]={}
                    self.tagdict[tag]['font_desc']=font.to_string()
                    self.tags[font.to_string()]=tag
                tags.append(self.tags[font.to_string()])
        if lang:
            if not self.tags.has_key(lang):
                tag = self.buf.create_tag()
                tag.set_property('language',lang)
                self.tags[lang]=tag
            tags.append(self.tags[lang])
        if attrs:
            for a in attrs:
                if a.type == pango.ATTR_FOREGROUND:
                    gdkcolor = self.pango_color_to_gdk(a.color)
                    key = 'foreground%s'%self.color_to_hex(gdkcolor)
                    if not self.tags.has_key(key):
                        self.tags[key]=self.buf.create_tag()
                        self.tags[key].set_property('foreground-gdk',gdkcolor)
                        self.tagdict[self.tags[key]]={}
                        self.tagdict[self.tags[key]]['foreground'] = \
                            "#%s"%self.color_to_hex(gdkcolor)
                    tags.append(self.tags[key])
                if a.type == pango.ATTR_BACKGROUND:
                    gdkcolor = self.pango_color_to_gdk(a.color)
                    tag.set_property('background-gdk',gdkcolor)
                    key = 'background%s'%self.color_to_hex(gdkcolor)
                    if not self.tags.has_key(key):
                        self.tags[key]=self.buf.create_tag()
                        self.tags[key].set_property('background-gdk',gdkcolor)
                        self.tagdict[self.tags[key]]={}
                        self.tagdict[self.tags[key]]['background']= \
                            "#%s"%self.color_to_hex(gdkcolor)
                    tags.append(self.tags[key])
                if self.pango_translation_properties.has_key(a.type):
                    prop=self.pango_translation_properties[a.type]
                    #print 'setting property %s of %s (type: %s)' % (
                    #    prop,a,a.type)
                    val=getattr(a,'value')
                    #tag.set_property(prop,val)
                    mval = val
                    if self.attval_to_markup.has_key(prop):
                        #print 'converting ', prop,' in ', val
                        if self.attval_to_markup[prop].has_key(val):
                            mval = self.attval_to_markup[prop][val]
                    key="%s%s"%(prop,val)
                    if not self.tags.has_key(key):
                        self.tags[key]=self.buf.create_tag()
                        self.tags[key].set_property(prop,val)
                        self.tagdict[self.tags[key]]={}
                        self.tagdict[self.tags[key]][prop]=mval
                    tags.append(self.tags[key])
                else:
                    print "Don't know what to do with attr %s" % a
        return tags

    def get_tags(self):
        tagdict = {}
        for pos in range(self.buf.get_char_count()):
            iter = self.buf.get_iter_at_offset(pos)
            for tag in iter.get_tags():
                if tagdict.has_key(tag):
                    if tagdict[tag][-1][1] == pos - 1:
                        tagdict[tag][-1] = (tagdict[tag][-1][0], pos)
                    else:
                        tagdict[tag].append((pos, pos))
                else:
                    tagdict[tag]=[(pos ,pos)]
        return tagdict

    def get_text(self):
        tagdict = self.get_tags()
        txt = self.buf.get_text(
            self.buf.get_start_iter(),self.buf.get_end_iter())
        cuts = {}
        for k,v in tagdict.items():
            stag,etag = self.tag_to_markup(k)
            for st,end in v:
                if cuts.has_key(st):
                    cuts[st].append(stag) #add start tags second
                else:
                    cuts[st] = [stag]
                if cuts.has_key(end+1):
                    cuts[end+1] = [etag]+cuts[end+1] #add end tags first
                else: cuts[end+1] = [etag]
        last_pos = 0
        outbuff = ""
        cut_indices = cuts.keys()
        cut_indices.sort()
        for c in cut_indices:
            #print 'cut',c
            if not last_pos == c:
                outbuff += txt[last_pos:c]
                last_pos = c
            for tag in cuts[c]:
                outbuff += tag
            #print 'outbuff: ',outbuff
        outbuff += txt[last_pos:]
        return outbuff

    def tag_to_markup(self, tag):
        stag = "<span"
        for k,v in self.tagdict[tag].items():
            stag += ' %s="%s"'%(k,v)
        stag += ">"
        return stag,"</span>"

    def fontdesc_to_attrs(self, font):
        nicks = font.get_set_fields().value_nicks
        attrs = []
        for n in nicks:
            if self.desc_to_attr_table.has_key(n):
                #print 'attributeifying %s'%n
                Attr,norm = self.desc_to_attr_table[n]
                # create an attribute with our current value
                attrs.append(Attr(getattr(font, 'get_%s'%n)()))
                # unset our font's value
                getattr(font, 'set_%s'%n)(norm)
        return font, attrs

    def pango_color_to_gdk(self, pc):
        return gtk.gdk.Color(pc.red,pc.green,pc.blue)

    def color_to_hex(self, color):
        hexstring = ""
        for col in 'red','green','blue':
            hexfrag = hex(getattr(color, col)/(16*16)).split("x")[1]
            if len(hexfrag) < 2:
                hexfrag = "0" + hexfrag
            hexstring += hexfrag
        #print 'returning hexstring: ', hexstring
        return hexstring

    def apply_font_and_attrs(self, font, attrs):
        tags = self.get_tags_from_attrs(font, None, attrs)
        for tag in tags:
            self.apply_tag(tag)

    def remove_font_and_attrs(self, font, attrs):
        tags = self.get_tags_from_attrs(font, None, attrs)
        for tag in tags:
            self.remove_tag(tag)

    def setup_default_tags(self):
        self.italics = self.get_tags_from_attrs(
            None, None, [pango.AttrStyle('italic')])[0]
        self.bold = self.get_tags_from_attrs(
            None, None, [pango.AttrWeight('bold')])[0]
        self.underline = self.get_tags_from_attrs(
            None, None, [pango.AttrUnderline('single')])[0]

    def get_selection(self):
        bounds = self.buf.get_selection_bounds()
        if not bounds:
            iter=self.buf.get_iter_at_mark(self.insert)
            if iter.inside_word():
                start_pos = iter.get_offset()
                iter.forward_word_end()
                word_end = iter.get_offset()
                iter.backward_word_start()
                word_start = iter.get_offset()
                iter.set_offset(start_pos)
                bounds = (self.buf.get_iter_at_offset(word_start),
                          self.buf.get_iter_at_offset(word_end))
        return bounds

    def apply_tag(self, tag):
        selection = self.get_selection()
        if selection:
            self.buf.apply_tag(tag, *selection)

    def remove_tag(self, tag):
        selection = self.get_selection()
        if selection:
            self.buf.remove_tag(tag,*selection)

    def remove_all_tags(self):
        selection = self.get_selection()
        if selection:
            for t in self.tags.values():
                self.buf.remove_tag(t,*selection)


class InteractivePangoBuffer(PangoBuffer):
    """An interactive interface to allow marking up a gtk.TextBuffer."""

    def __init__(self, txt, buf, normal_button=None, toggle_widget_alist=[]):
        """
        txt is initial text, with markup.
        buf is the gtk.TextBuffer
        normal_button is a widget whose clicked signal will make us normal
        toggle_widget_alist is a list that looks like this:
            [(widget, (font,attr)),
             (widget2, (font,attr))]
        """
        PangoBuffer.__init__(self, txt, buf)
        if normal_button:
            normal_button.connect('clicked', actions.doNormalize, self)
        self.tag_widgets = {}
        self.internal_toggle = False
        self.insert = self.buf.get_insert()
        self.buf.connect('mark-set', actions.doSetMark, self)
        for w, tup in toggle_widget_alist:
            self.setup_widget(w, *tup)

    def setup_widget_from_pango(self, widg, markupstring):
        """setup widget from a pango markup string"""
        a, t, s = pango.parse_markup(markupstring, u'0')
        ai = a.get_iterator()
        font, lang, attrs = ai.get_font()
        self.setup_widget(widg, font, attrs)

    def setup_widget(self, widg, font, attr):
        tags = self.get_tags_from_attrs(font, None, attr)
        self.tag_widgets[tuple(tags)] = widg
        widg.connect('toggled', actions.doToggleButton, tags, self)


class SimpleEditor(object):

    def __init__(self):
        self.widgetTree = glade.XML("pango-richtext.glade")
        window = self.getWidget("window1")
        window.connect('delete-event', actions.doQuit)
        self.pangoBuffer = InteractivePangoBuffer(
            INITIAL_TEXT,
            self.getWidget("textview1").get_buffer(),
            normal_button=self.getWidget("button5"))
        self.setupMenuActions()
        self.setupButtonActions()
        self.setupFormatting()
        window.show_all()

    def getWidget(self, name):
        return self.widgetTree.get_widget(name)

    def populateFontMenu(self):
        pass

    def setupMenuActions(self):
        # File menu item
        self.getWidget("new1").connect("activate", actions.doNew)
        self.getWidget("open1").connect("activate", actions.doOpen)
        self.getWidget("save1").connect("activate", actions.doSave)
        self.getWidget("save_as1").connect("activate", actions.doSaveAs)
        self.getWidget("quit1").connect("activate", actions.doQuit)
        # Format menu item
        self.getWidget("increase_font_size1").connect(
            "activate", actions.doIncreaseFontSize, self.pangoBuffer)


    def setupButtonActions(self):
        debugMarkup = self.getWidget("button11")
        debugMarkup.connect('clicked', actions.doPrintMarkup, self.pangoBuffer)

    def setupFormatting(self):
        formatButtons = self.getWidget("hbuttonbox1").get_children()
        buttonData = {
            'gtk-italic': '<i>italic</i>',
            'gtk-bold': '<b>bold</b>',
            'gtk-underline': '<u>underline</u>',
            'Blue': '<span foreground="blue">blue</span>',
            'Red': '<span foreground="red">red</span>',
            }
        for button in formatButtons:
            font = buttonData.get(button.get_label())
            if font:
                self.pangoBuffer.setup_widget_from_pango(button, font)


if __name__ == '__main__':
    se = SimpleEditor()
    gtk.main()
